package cn.bailan.service.impl;
import cn.bailan.entity.Constant;
import cn.bailan.entity.MoralBalance;
import cn.bailan.entity.R.R;
import cn.bailan.entity.User;
import cn.bailan.mapper.UserMapper;
import cn.bailan.service.IUserService;
import cn.bailan.utils.*;
import cn.bailan.utils.redis.RedisUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.servlet.http.HttpServletRequest;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
 * Author:甲粒子
 * Date: 2022/6/4
 * Description：
 */
@Service
public class UserServiceImpl implements IUserService {
    @Autowired
    private RedisUtil redis;
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private MongoTemplate mongo;

    @Override
    public Object wxLogin(String data, HttpServletRequest request) {
        String token = request.getHeader("token");
        //携带code iv encryptedData
        ObjectNode node = null;
        //微信服务器返回的数据有sessionKey和openId
        ObjectNode dataNode = null;
        try{
            node = new ObjectMapper().readValue(data, ObjectNode.class);
            String _data_ = requestWX(data, node, new HashMap<>());
            //System.out.println(_data_);
            dataNode = resolveData(_data_);
            String openId = dataNode.get("openid").toString();
            if(token == null || "".equals(token)){
                //不是第一次登录
                if (existUser(openId) != 0) {
                    //更新登录时间并返回新token
                    User user = selectUser(openId);
                    Integer userId = user.getUserId();
                    //查看昨天是否登录
                   if(getRTime(userId) != null && getRTime(userId).equals(TimeUtil.yesterday())){
                       inrLoginDays(userId);
                   }else {
                       resetLoginDaysAndScore(userId);
                   }
                    setRTime(userId,TimeUtil.getNowTime());
                    String newToken = JWTUtil.doCreateToken(new HashMap<>(), userId);
                    Map<String,Object> returnMap = new HashMap<>();
                    returnMap.put("token",newToken);
                    returnMap.put("id",userId);
                    returnMap.put("score",getScore(userId));
                    returnMap.put("nickName",user.getNickName());
                    returnMap.put("gender",user.getGender());
                    return R.ok().data(returnMap);
                }

                //是第一次登录

                //node指向解密后的数据节点
                //包含昵称等信息
                // _data_ = userService.requestWX(data, node, new HashMap<>());
                //dataNode = userService.resolveData(_data_);

                node = resolveEncryptedData(node,dataNode);
                //新建用户
                User user = new User();
                user.setNickName(node.get("nickName").toString());
                user.setGender(Integer.parseInt(String.valueOf(node.get("gender"))));
                user.setAvatar(node.get("avatarUrl").toString());
                String today = TimeUtil.getNowTime();
                user.setCTime(today);
                user.setRTime(today);
                user.setOpenId(openId);
                //生成userId
                Integer userId = 0;
                do {
                    userId = IdUtil.generateUserId();
                }while (redis.getBit("userIds",userId));//自旋
                redis.setBit("userIds",userId,true);
                user.setUserId(userId);
                user.setLoginDays(1);
                newUser(user);//新增用户

                //返回token
                String userToken = JWTUtil.doCreateToken(new HashMap<>(), userId);
                Map<String,Object> returnMap = new HashMap<>();
                returnMap.put("token",userToken);
                returnMap.put("id",userId);
                returnMap.put("score",getScore(userId));
                returnMap.put("nickName",user.getNickName());
                returnMap.put("gender",user.getGender());
                return R.ok().data(returnMap);
            }
            //不是第一次登录
            else {
                //更新登录时间并返回新token
                User user = selectUser(openId);
                Integer userId = user.getUserId();
                if(getRTime(userId) != null && getRTime(userId).equals(TimeUtil.yesterday())){
                    inrLoginDays(userId);
                }else {
                    resetLoginDaysAndScore(userId);
                }
                setRTime(userId,TimeUtil.getNowTime());
                String newToken = JWTUtil.doCreateToken(new HashMap<>(), userId);
                Map<String,Object> returnMap = new HashMap<>();
                returnMap.put("token",newToken);
                returnMap.put("id",userId);
                returnMap.put("score",getScore(userId));
                returnMap.put("nickName",user.getNickName());
                returnMap.put("gender",user.getGender());
                return R.ok().data(returnMap);
            }

        }catch (Exception e){
            e.printStackTrace();
            return R.error();
        }
    }

    @Override
    public String requestWX(String data, ObjectNode node, Map<String, String> params) {
        try {
            params.put("appid", Constant.APP_ID);
            params.put("secret",Constant.APP_SECRET);
            params.put("js_code",node.get("code").toString().replace("\"", ""));
            params.put("grant_type","authorization_code");
            /**
             * 向微信服务器发送请求
             */
            return HttpClientUtil.doGet(Constant.REQUEST_FOR_WX,params);
        } catch ( KeyStoreException | NoSuchAlgorithmException | KeyManagementException e ) {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public ObjectNode resolveData(String data) {
        try {
            return new ObjectMapper().readValue(data, ObjectNode.class);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public ObjectNode resolveEncryptedData(ObjectNode data,ObjectNode node) {
        try {
            String decrypt = AesCbcUtil.decrypt(data.get("encryptedData").toString(), node.get("session_key").toString(), data.get("iv").toString(), "utf-8");
            return new ObjectMapper().readValue(decrypt, ObjectNode.class);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public int newUser(User user) {
        return userMapper.newUser(user);
    }

    @Override
    public int existUser(String openId) {
        return userMapper.existUser(openId);
    }

    @Override
    public Integer selectUserId(String openId) {
        return userMapper.selectUserId(openId);
    }

    @Override
    public User selectUser(String openId) {
        return userMapper.selectUser(openId);
    }

    @Override
    public int setNickName(Integer userId, String nickName) {
        return userMapper.setNickName(userId, nickName);
    }

    @Override
    public int setAvatar(Integer userId, String avatar) {
        return userMapper.setAvatar(userId, avatar);
    }

    @Override
    public int setRTime(Integer userId, String rTime) {
        return userMapper.setRTime(userId,rTime);
    }

    @Override
    public int updateUserInfo(User user) {
        return userMapper.updateUserInfo(user);
    }

    @Override
    public User getUserInfo(Integer userId) {
        return userMapper.getUserInfo(userId);
    }

    @Override
    public long countUser() {
        return userMapper.countUser();
    }

    @Override
    public List<User> getTop25Good() {
        return userMapper.getTop25Good();
    }

    @Override
    public List<User> getTop25Bad() {
        return userMapper.getTop25Bad();
    }

    @Override
    public List<User> getUserList(long curPage, int pageSize) {
        return userMapper.getUserList(curPage, pageSize);
    }

    @Override
    public int updateScore(Integer userId, double scoreChange) {
        return userMapper.updateScore(userId, scoreChange);
    }

    @Override
    public double getScore(Integer userId) {
        return userMapper.getScore(userId);
    }

    @Override
    public int inrLoginDays(Integer userId) {
        return userMapper.inrLoginDays(userId);
    }

    @Override
    public int resetLoginDays(Integer userId) {
        return userMapper.resetLoginDays(userId);
    }

    @Override
    public String getRTime(Integer userId) {
        return userMapper.getRTime(userId);
    }

    @Override
    public String getInspiration() {
        return userMapper.getInspiration();
    }

    @Override
    public void addMoralBalance(MoralBalance moralBalance) {
        if(!existOne(moralBalance)){
            moralBalance.setUpTime(TimeUtil.getTime());
            mongo.insert(moralBalance);
        }
    }

    @Override
    public boolean existOne(MoralBalance moralBalance) {
        Query query = new Query();
        query.addCriteria(Criteria.where("userId").is(moralBalance.getUserId()).and("upTime").is(moralBalance.getUpTime()));
        List<MoralBalance> moralBalances = mongo.find(query, MoralBalance.class);
        if(moralBalances.size() != 0)
            return true;
        return false;
    }

    @Override
    public List<MoralBalance> getMoralBalanceList(Integer userId, long curPage, int pageSize) {
        Query query = new Query();
        //Sort sort=Sort.by(Sort.Direction.DESC,"upTime");
        Sort sort = Sort.by(Sort.Order.desc("upTime"));
        query.addCriteria(Criteria.where("userId").is(userId));
        return mongo.find(query.with(sort).skip((curPage-1)*pageSize).limit(pageSize), MoralBalance.class);
    }

    @Override
    public long countMoralBalance(Integer userId) {
        Query query = new Query();
        query.addCriteria(Criteria.where("userId").is(userId));
        return mongo.count(query, MoralBalance.class);
    }

    @Override
    public int resetScore(Integer userId) {
        return userMapper.resetScore(userId);
    }

    @Transactional
    public void  resetLoginDaysAndScore(Integer userId){
        resetLoginDays(userId);
        resetScore(userId);
    }
}
